package org.flashIso.engine.base {
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.utils.getDefinitionByName;
	
	import org.flashIso.engine.core.ILibrary;
	import org.flashIso.engine.core.ISerializableXML;
	import org.flashIso.engine.core.IsoObjectBase;
	import org.flashIso.engine.core.Point2D;
	import org.flashIso.engine.core.Point3D;
	import org.flashIso.engine.core.ValidateableSprite;
	import org.flashIso.engine.events.IsoEvent;
	import org.flashIso.engine.objects.IsoObject;
	import org.flashIso.engine.objects.IsoTile;

	/**
	 * 
	 * IsoMap is a container and manager for IsoObjectBase objects. 
	 * Depth and flash coordinates are automaticaly adjusted based on.
	 * objects' position property and map's perspective.
	 * Use addElement and removeElement functions instead of addChild / removeChild.
	 * When perspective object changes draw method is called for all childred.
	 * 
	 */
	public class IsoMap extends ValidateableSprite implements ISerializableXML{
		protected var _perspective : IsoPerspective = new IsoPerspective();
		
		protected var _xAxis : IsoObject;
		protected var _yAxis : IsoObject;
		protected var _zAxis : IsoObject;
		
		protected var _showAxis:Boolean = false;
		
		protected var _axisInvalidated:Boolean = false;
		protected var _childredInvalidated:Boolean = false;
		
		public function IsoMap() {
			_perspective.addEventListener(Event.CHANGE, perspectiveChangeHandler, false, 0, true);
		}

		protected function perspectiveChangeHandler(e:Event) : void {
			invalidateChildren();
		}
		
		
		/**
		 *
		 * Setting this property true will create 3 objects of colors 
		 * red, green, blue representing X, Y and Z axis.
		 *  
		 **/
		public function set showAxis(value : Boolean) : void {
			if (_showAxis != value) {
				_showAxis = value;
				invalidateAxis();
			}
		}
		public function get showAxis() : Boolean {
			return _showAxis;
		}
		
		/**
		 * 
		 * Reference to perspective instance
		 * 
		 **/
		public function set perspective(perspective : IsoPerspective) : void {
			_perspective.removeEventListener(Event.CHANGE, triggerValidation);
			_perspective = perspective;
			_perspective.addEventListener(Event.CHANGE, triggerValidation, false, 0, true);
			invalidateChildren();
		}
		public function get perspective() : IsoPerspective {
			return _perspective;
		}
		
		
		public function addElement(child : IsoObjectBase) : void {
			super.addChildAt(child, determineLevelForNewChild(child));
			//super.addChild(child);
			
			updateChildPosition(child);
			drawChild(child);

			child.addEventListener(IsoEvent.POSITION_CHANGE, childPositionChangeHandler, false, 0, true);
			child.addEventListener(Event.CHANGE, childChangeHandler, false, 0, true);
		}
		
		protected function childChangeHandler (e:Event) : void {
			drawChild(e.target as IsoObjectBase);
		}
		
		public function removeElement(child:IsoObjectBase):void {
			super.removeChild(child);
			child.removeEventListener(IsoEvent.POSITION_CHANGE, childPositionChangeHandler);
			child.removeEventListener(Event.CHANGE, triggerValidation);
		}
		
		
		/**
		 * Use addElement function insted of addChild
		 **/
		override public function addChild(child : DisplayObject) : DisplayObject {
			throw (new Error("Method addChild is not available! Use addElement instead."));
			return null;
		}
		
		/**
		 * Use addElement function insted. Note that objects 
		 * depth is automaticaly handled by IsoMap container
		 **/
		override public function addChildAt(child : DisplayObject, index : int) : DisplayObject {
			throw (new Error("Method addChildAt is not available! Use addElement instead."));
			return null;
		}
		
		/**
		 * Use removeElement function instead
		 **/
		override public function removeChild(child:DisplayObject):DisplayObject {
			throw (new Error("Method removeChild is not available! Use removeElement instead."));
		}
		
		/**
		 * Use removeElement function instead
		 **/
		override public function removeChildAt(index:int):DisplayObject {
			throw (new Error("Method removeChildAt is not available! Use removeElement instead."));
		}
		
		protected function invalidateChildren() : void {
			if (_childredInvalidated == false){
				_childredInvalidated = true;
				triggerValidation();
			}
		}
		protected function validateChildrenNow() : void {
			if (_childredInvalidated){
				_childredInvalidated = false;
				var i:uint;
				for (i = 0; i < numChildren; i++){
					if (getChildAt(i) is IsoObjectBase){
						drawChild(getChildAt(i) as IsoObjectBase);
						updateChildPosition(getChildAt(i) as IsoObjectBase);
					}
				}
			}
		}

		protected function invalidateAxis() : void {
			if (_axisInvalidated == false){
				_axisInvalidated = true;
				triggerValidation();
			}
		}
		protected function validateAxisNow() : void {
			if (_axisInvalidated){
				_axisInvalidated = false;
				if(_xAxis != null){
					removeElement(_xAxis);
					removeElement(_yAxis);
					removeElement(_zAxis);
					_xAxis = _yAxis = _zAxis = null;
				}
				if (_showAxis){
					_xAxis = new IsoObject();
					_xAxis.size = new Point3D(100, 5, 5);
					_xAxis.fillColor = 0xff0000;
					_xAxis.fillAlpha = 0.2;
					_xAxis.lineColor = 0xff0000;
					_xAxis.lineAlpha = 0.6;
					addElement(_xAxis);
					
					_yAxis = new IsoObject();
					_yAxis.size = new Point3D(5, 100, 5);
					_yAxis.fillColor = 0x00ff00;
					_yAxis.fillAlpha = 0.2;
					_yAxis.lineColor = 0x00ff00;
					_yAxis.lineAlpha = 0.6;
					addElement(_yAxis);
					
					_zAxis = new IsoObject();
					_zAxis.size = new Point3D(5, 5, 100);
					_zAxis.fillColor = 0x0000ff;
					_zAxis.fillAlpha = 0.2;
					_zAxis.lineColor = 0x0000ff;
					_zAxis.lineAlpha = 0.6;
					addElement(_zAxis);
				}
			}
		}
		
		override public function validateNow() : void {
			validateAxisNow();
			validateChildrenNow();
			super.validateNow();
		}

		protected function childChange(e:Event) : void {
			drawChild(e.target as IsoObjectBase);
		}

		protected function drawChild(child : IsoObjectBase) : void {
			child.draw(perspective);
		}


		protected function childPositionChangeHandler(e:Event) : void {
			updateChildPosition(e.target as IsoObjectBase);
		}
		protected function updateChildPosition(object : IsoObjectBase) : void {
			updateObjectZOrder(object);
			var p : Point2D = perspective.getProjection(object.position);
			object.x = p.x;
			object.y = p.y;
		}

		protected function determineLevelForNewChild(child : IsoObjectBase) : uint {
			var pos:Point3D = child.position;
			var min : int = 0;
			var max : int = numChildren;
			var middle : int;
			if (numChildren == 0) {
				return 0;
			}
			while (max - min > 1) {
				middle = min + (max - min) / 2;
				if (getPointDepthValue((getChildAt(middle) as IsoObjectBase).position) < getPointDepthValue(pos)) {
					max = middle;
				} else {
					min = middle;
				}
			}
			if (getPointDepthValue((getChildAt(min) as IsoObjectBase).position) < getPointDepthValue(pos)) {
				return min;
			}
			return max;
		}

//		protected function updateObjectZOrder(object : IsoObjectBase) : void {
//			var level : int = getChildIndex(object);
//			while ((level < (numChildren - 1)) && ((!(getChildAt(level + 1) is IsoObjectBase)) || (getPointDepthValue((getChildAt(level + 1) as IsoObjectBase).position) > getPointDepthValue(object.position) ))) {
//				level++;
//			}
//			if (level != getChildIndex(object)) {
//				super.addChildAt(object, level);
//			}
//
//			level = getChildIndex(object);
//			while ((level > 0) && ((!(getChildAt(level - 1) is IsoObjectBase)) || (getPointDepthValue((getChildAt(level - 1) as IsoObjectBase).position) < getPointDepthValue(object.position)))) {
//				level--;
//			}
//			if (level != getChildIndex(object)) {
//				super.addChildAt(object, level);
//			}
//		}

//		protected function updateObjectZOrder(object : IsoObjectBase) : void {
//			var x:Number = object.position.x;
//			var y:Number = object.position.y;
//			var width:Number = object.size.x;
//			var height:Number = object.size.y;			
//			var level : int = getChildIndex(object);			
//			var child:IsoObjectBase;
//			
//			if (level < (numChildren - 1)) {
//				 child = getChildAt(level + 1) as IsoObjectBase;
//			}				
//			while ( (child != null) && 
//					((x + width < child.position.x) ||
//					(y + height < child.position.y)) ) {
//				
//				level++;				
//				if (level < (numChildren - 1)) {
//					child = getChildAt(level + 1) as IsoObjectBase;
//				}else{
//					child = null;
//				}
//			}			
//			if (level > numChildren - 1){
//				level = numChildren - 1;
//			}
//
//			if (level != getChildIndex(object)) {
//				super.addChildAt(object, level);
//			}
//
//			
//			level = getChildIndex(object);
//			
//			if (level > 0){
//				child = getChildAt(level - 1) as IsoObjectBase;
//			}
//			
//			while ( (child != null) && 
//				((child.position.x + child.size.x <= x) ||
//				(child.position.y + child.size.y <= y)) ) {				
//				level--;
//				if (level > 0) {
//					child = getChildAt(level - 1) as IsoObjectBase;
//				}else{
//					child = null;
//				}
//			}
//			if (level < 0){
//				level = 0;
//			}
//			if (level != getChildIndex(object)) {
//				super.addChildAt(object, level);
//			}
//		}
		
		protected function updateObjectZOrder(object : IsoObjectBase) : void {
			var i:uint;
			for (i = 0; i < numChildren; i++) {
				var child:IsoObjectBase = getChildAt(i) as IsoObjectBase;				
				if (child != null && child.hitTestObject(object)) {
					if ((child.position.x + child.size.x <= object.position.x) ||
						(child.position.y + child.size.y <= object.position.y)) {
						if (getChildIndex(child) < getChildIndex(object)){
							if (getChildIndex(child) > 0){
								super.addChildAt(object, getChildIndex(child) - 1);
							}else{
								super.addChildAt(object, 0);
							}							
						}
					}else
					if ((object.position.x + object.size.x < child.position.x) ||
						(object.position.y + object.size.y < child.position.y)) {
						if (getChildIndex(child) > getChildIndex(object)){
							super.addChildAt(object, getChildIndex(child));
						}
					}
				}
			}
		}
		
		protected function getPointDepthValue(obj : Point3D) : Number {			
			//return obj.x + obj.y * 100000;
			return obj.y;
		}
		
		public function toXML() : XML {
			var xml:XML = new XML("<" + className + "  />");
			xml.appendChild(perspective.toXML());
			
			var i:uint;
			for (i = 0; i < numChildren; i++ ) {
				if (getChildAt(i) is ISerializableXML) {
					xml.appendChild((getChildAt(i) as ISerializableXML).toXML());
				}
			}
			return xml;
		}
		
		public function fromXML(xml:XML, library:ILibrary = null) : void {
			perspective.fromXML(xml.child(perspective.className)[0], library);
			
			while (numChildren > 0){
				removeElement(getChildAt(0) as IsoObjectBase);
			}
			var i:uint;
			for (i = 0; i < xml.children().length(); i++){
				var childXML:XML = xml.children()[i];
				if (childXML.name() == "IsoTile"){
					var tile:IsoTile = new IsoTile();
					tile.fromXML(childXML, library);
					addElement(tile);
				}
				if (childXML.name() == "IsoObject"){
					var object:IsoObject = new IsoObject();
					object.fromXML(childXML, library);
					addElement(object);
				}
				
			}
		}
	}
}
